﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.Serialization;
using System.Web.Mvc;

namespace ManagedUIFlow.Model
{
    [Serializable]
    public class ManagedFlowDataDictionary : IDictionary<string, object>, ISerializable
    {
        internal const string TempDataSerializationKey = "__flowData";

        internal Dictionary<string, object> Data;
        private HashSet<string> _initialKeys;
        private readonly HashSet<string> _modifiedKeys;

        public ManagedFlowDataDictionary()
        {
            _initialKeys = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
            _modifiedKeys = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
            Data = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);            
        }

        protected ManagedFlowDataDictionary(SerializationInfo info, StreamingContext context)
        {
            _initialKeys = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
            _modifiedKeys = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
            Data = info.GetValue(TempDataSerializationKey, typeof(Dictionary<string, object>)) as Dictionary<string, object>;            
        }

        public int Count
        {
            [SuppressMessage("Microsoft.Security", "CA2123:OverrideLinkDemandsShouldBeIdenticalToBase")]
            get
            {
                return Data.Count;
            }
        }

        public Dictionary<string, object>.KeyCollection Keys
        {
            get
            {
                return Data.Keys;
            }
        }       

        public void Load(ControllerContext controllerContext, ITempDataProvider tempDataProvider)
        {
            var providerDictionary = tempDataProvider.LoadTempData(controllerContext);
            Data = (providerDictionary != null) ? new Dictionary<string, object>(providerDictionary, StringComparer.OrdinalIgnoreCase) :
                                                                                                                                           new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
            _initialKeys = new HashSet<string>(Data.Keys);
            _modifiedKeys.Clear();
        }

        public void Save(ControllerContext controllerContext, ITempDataProvider tempDataProvider)
        {
            if (_modifiedKeys.Count <= 0) return;
            // Apply change tracking.
            foreach (var x in _initialKeys)
            {
                if (!_modifiedKeys.Contains(x))
                {
                    Data.Remove(x);
                }
            }

            // Store the dictionary
            tempDataProvider.SaveTempData(controllerContext, Data);
        }

        public Dictionary<string, object>.ValueCollection Values
        {
            get
            {
                return Data.Values;
            }
        }

        public object this[string key]
        {
            [SuppressMessage("Microsoft.Security", "CA2123:OverrideLinkDemandsShouldBeIdenticalToBase")]
            get
            {
                object value;
                return TryGetValue(key, out value) ? value : null;
            }
            [SuppressMessage("Microsoft.Security", "CA2123:OverrideLinkDemandsShouldBeIdenticalToBase")]
            set
            {
                Data[key] = value;
                _modifiedKeys.Add(key);
            }
        }

        [SuppressMessage("Microsoft.Security", "CA2123:OverrideLinkDemandsShouldBeIdenticalToBase")]
        public void Add(string key, object value)
        {
            Data.Add(key, value);
            _modifiedKeys.Add(key);
        }

        [SuppressMessage("Microsoft.Security", "CA2123:OverrideLinkDemandsShouldBeIdenticalToBase")]
        public void Clear()
        {
            Data.Clear();
            _modifiedKeys.Clear();
            _initialKeys.Clear();
        }

        [SuppressMessage("Microsoft.Security", "CA2123:OverrideLinkDemandsShouldBeIdenticalToBase")]
        public bool ContainsKey(string key)
        {
            return Data.ContainsKey(key);
        }

        public bool ContainsValue(object value)
        {
            return Data.ContainsValue(value);
        }

        public Dictionary<string, object>.Enumerator GetEnumerator()
        {
            return Data.GetEnumerator();
        }

        protected virtual void GetObjectData(SerializationInfo info, StreamingContext context)
        {
            info.AddValue(TempDataSerializationKey, Data);
        }

        [SuppressMessage("Microsoft.Security", "CA2123:OverrideLinkDemandsShouldBeIdenticalToBase")]
        public bool Remove(string key)
        {
            _initialKeys.Remove(key);
            _modifiedKeys.Remove(key);
            return Data.Remove(key);
        }

        [SuppressMessage("Microsoft.Security", "CA2123:OverrideLinkDemandsShouldBeIdenticalToBase")]
        public bool TryGetValue(string key, out object value)
        {
            return Data.TryGetValue(key, out value);
        }

        ICollection<string> IDictionary<string, object>.Keys
        {
            [SuppressMessage("Microsoft.Security", "CA2123:OverrideLinkDemandsShouldBeIdenticalToBase")]
            get
            {
                return ((IDictionary<string, object>)Data).Keys;
            }
        }

        ICollection<object> IDictionary<string, object>.Values
        {
            [SuppressMessage("Microsoft.Security", "CA2123:OverrideLinkDemandsShouldBeIdenticalToBase")]
            get
            {
                return ((IDictionary<string, object>)Data).Values;
            }
        }

        [SuppressMessage("Microsoft.Security", "CA2123:OverrideLinkDemandsShouldBeIdenticalToBase")]
        IEnumerator<KeyValuePair<string, object>> IEnumerable<KeyValuePair<string, object>>.GetEnumerator()
        {
            return ((IEnumerable<KeyValuePair<string, object>>)Data).GetEnumerator();
        }

        bool ICollection<KeyValuePair<string, object>>.IsReadOnly
        {
            [SuppressMessage("Microsoft.Security", "CA2123:OverrideLinkDemandsShouldBeIdenticalToBase")]
            get
            {
                return ((ICollection<KeyValuePair<string, object>>)Data).IsReadOnly;
            }
        }

        [SuppressMessage("Microsoft.Security", "CA2123:OverrideLinkDemandsShouldBeIdenticalToBase")]
        void ICollection<KeyValuePair<string, object>>.CopyTo(KeyValuePair<string, object>[] array, int index)
        {
            ((ICollection<KeyValuePair<string, object>>)Data).CopyTo(array, index);
        }

        [SuppressMessage("Microsoft.Security", "CA2123:OverrideLinkDemandsShouldBeIdenticalToBase")]
        void ICollection<KeyValuePair<string, object>>.Add(KeyValuePair<string, object> keyValuePair)
        {
            _modifiedKeys.Add(keyValuePair.Key);
            ((ICollection<KeyValuePair<string, object>>)Data).Add(keyValuePair);
        }

        [SuppressMessage("Microsoft.Security", "CA2123:OverrideLinkDemandsShouldBeIdenticalToBase")]
        bool ICollection<KeyValuePair<string, object>>.Contains(KeyValuePair<string, object> keyValuePair)
        {
            return ((ICollection<KeyValuePair<string, object>>)Data).Contains(keyValuePair);
        }

        [SuppressMessage("Microsoft.Security", "CA2123:OverrideLinkDemandsShouldBeIdenticalToBase")]
        bool ICollection<KeyValuePair<string, object>>.Remove(KeyValuePair<string, object> keyValuePair)
        {
            _modifiedKeys.Remove(keyValuePair.Key);
            return ((ICollection<KeyValuePair<string, object>>)Data).Remove(keyValuePair);
        }

        [SuppressMessage("Microsoft.Security", "CA2123:OverrideLinkDemandsShouldBeIdenticalToBase")]
        IEnumerator IEnumerable.GetEnumerator()
        {
            return ((IEnumerable)Data).GetEnumerator();
        }

        [SuppressMessage("Microsoft.Security", "CA2123:OverrideLinkDemandsShouldBeIdenticalToBase")]
        void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
        {
            GetObjectData(info, context);
        }
    }
}